`include "define.svh"

module id(
    // Current PC & instr and whether this instr in delay slot
    input wire [`PC_WIDTH - 1 : 0]      pc_i,
    input wire [`INST_WIDTH - 1 : 0]	inst_i,
    output reg                          is_branch_inst,
    // The read address of both registers and the value returned from the register.
    output reg [`REG_ADDR_WIDTH - 1 : 0]    raddr1_o,
    output reg [`REG_ADDR_WIDTH - 1 : 0]    raddr2_o,
    input wire [`REG_WIDTH - 1 : 0]         reg_rdata1_i,
    input wire [`REG_WIDTH - 1 : 0]         reg_rdata2_i,
    // The value of the HILO register.
    input wire [`REG_WIDTH - 1 : 0]         rhidata_i,
    input wire [`REG_WIDTH - 1 : 0]         rlodata_i,
    // Two opprands and op-code sent to ALU (EX stage).
    output reg [`REG_WIDTH - 1 : 0]         opprand1_o,
    output reg [`REG_WIDTH - 1 : 0]         opprand2_o,
    output reg [`ALUOP_WIDTH - 1 : 0]       aluop_o,
    // CP0 read signal and value returned.
    output reg [`REG_ADDR_WIDTH - 1 : 0]    cp0_raddr_o,
    input wire [`REG_WIDTH - 1 : 0]         cp0_rdata_i,
    // Data read enable (L-instr, write to reg) and whether signed-extension is needed.
    output reg [3 : 0]                      rdataen_o,
    output reg                              rdatasigned_o,
    // Register write addr and enable signal.
    output reg [`REG_ADDR_WIDTH - 1 : 0]    wregaddr_o,
    output reg                              wregenable_o,
    // HILO register write addr and enable signal.
    output reg                              whien_o,
    output reg                              wloen_o,
    output reg [`REG_WIDTH - 1 : 0]         whilodata_o,
    // Data SRAM wtite enable and data (addr will be calculated at EX stage).
    output reg [3 : 0]                      wdataen_o,
    output reg [`REG_WIDTH - 1 : 0]         wdata_o,
    // CP0 write enable sinal, addr and data.
    output reg                              mtc_wen,
    output reg [`REG_ADDR_WIDTH - 1 : 0]    mtc_waddr,
    output reg [`REG_WIDTH - 1 : 0]         mtc_wdata,
    // Branch enable and addr.
    output reg                              branch_flag_o,
    output reg [`PC_WIDTH - 1 : 0]          branch_addr_o,
    // Exception detection.
    output reg                              Sys_exc,
    output reg                              Bp_exc,
    output reg                              RI_exc,
    output reg                              ERET_exc
);
    
    /* ******************** INST ******************** */
    wire [5 : 0] opcode = inst_i[31 : 26];
    wire [5 : 0] func = inst_i[5 : 0];
    wire rinst = (opcode == 6'b000000);
    
    reg [6 : 0]     is_inst;

    wire ADD     = (rinst && func == 6'b100000);
    wire ADDI    = (opcode == 6'b001000);
    wire ADDU    = (rinst && func == 6'b100001);
    wire ADDIU   = (opcode == 6'b001001);
    wire SUB     = (rinst && func == 6'b100010);
    wire SUBU    = (rinst && func == 6'b100011);
    wire SLT     = (rinst && func == 6'b101010);
    wire SLTI    = (opcode == 6'b001010);
    wire SLTU    = (rinst && func == 6'b101011);
    wire SLTIU   = (opcode == 6'b001011);
    wire DIV     = (rinst && func == 6'b011010);
    wire DIVU    = (rinst && func == 6'b011011);
    wire MULT    = (rinst && func == 6'b011000);
    wire MULTU   = (rinst && func == 6'b011001);
    always_comb 
        is_inst[0] = |{ADD, ADDI, ADDIU, ADDU, SUB,SUBU, SLT, SLTI, SLTU, SLTIU, DIV, DIVU, MULT, MULTU}; 
    
    wire ANDi    = (rinst && func == 6'b100100);
    wire ANDI    = (opcode == 6'b001100);
    wire NORi    = (rinst && func == 6'b100111);
    wire ORi     = (rinst && func == 6'b100101);
    wire ORI     = (opcode == 6'b001101);
    wire XORi    = (rinst && func == 6'b100110);
    wire XORI    = (opcode == 6'b001110);
    wire LUI     = (opcode == 6'b001111);
    always_comb
        is_inst[1] = |{ANDi, ANDI, NORi, ORi, ORI, XORi, XORI, LUI};
    
    wire NOP     = (inst_i == 32'h0000_0000);
    wire SLLV    = (rinst && func == 6'b000100);
    wire SLL     = (~NOP) && (rinst && func == 6'b000000);
    wire SRAV    = (rinst && func == 6'b000111);
    wire SRA     = (rinst && func == 6'b000011);
    wire SRLV    = (rinst && func == 6'b000110);
    wire SRL     = (rinst && func == 6'b000010);
    always_comb
        is_inst[2] = |{SLLV, SLL, SRAV, SRA, SRLV, SRL, NOP};

    wire BEQ     = (opcode == 6'b000100);
    wire BNE     = (opcode == 6'b000101);
    wire BGEZ    = (opcode == 6'b000001 && inst_i[20 : 16] == 5'b00001);
    wire BGTZ    = (opcode == 6'b000111 && inst_i[20 : 16] == 5'b00000);
    wire BLEZ    = (opcode == 6'b000110 && inst_i[20 : 16] == 5'b00000);
    wire BLTZ    = (opcode == 6'b000001 && inst_i[20 : 16] == 5'b00000);
    wire BGEZAL  = (opcode == 6'b000001 && inst_i[20 : 16] == 5'b10001);
    wire BLTZAL  = (opcode == 6'b000001 && inst_i[20 : 16] == 5'b10000);
    wire J       = (opcode == 6'b000010);
    wire JAL     = (opcode == 6'b000011);
    wire JR      = (rinst && func == 6'b001000);
    wire JALR    = (rinst && func == 6'b001001);
    always_comb
        is_inst[3] = |{BEQ, BNE, BGEZ, BGTZ, BLEZ, BLTZ, BGEZAL, BLTZAL, J, JAL, JR, JALR};
    
    wire MFHI    = (rinst && func == 6'b010000);
    wire MFLO    = (rinst && func == 6'b010010);
    wire MTHI    = (rinst && func == 6'b010001);
    wire MTLO    = (rinst && func == 6'b010011);
    always_comb
        is_inst[4] = |{MFHI, MFLO, MTHI, MTLO};
    

    wire LB      = (opcode == 6'b100000);
    wire LBU     = (opcode == 6'b100100);
    wire LH      = (opcode == 6'b100001);
    wire LHU     = (opcode == 6'b100101);
    wire LW      = (opcode == 6'b100011);
    wire SB      = (opcode == 6'b101000);
    wire SH      = (opcode == 6'b101001);
    wire SW      = (opcode == 6'b101011);
    always_comb
        is_inst[5] = |{LB, LBU, LH, LHU, LW, SB, SH, SW};

    // Other instruction
    wire BREAKi  = (rinst && func == 6'b001101);
    wire SYSCALL = (rinst && func == 6'b001100);
    wire ERET    = (inst_i == 32'b010000_1_0000000000000000000_011000);
    wire MFC0    = (inst_i[31 : 21] == 11'b010000_00000 && inst_i[10 : 3] == 8'b0000_0000);
    wire MTC0    = (inst_i[31 : 21] == 11'b010000_00100 && inst_i[10 : 3] == 8'b0000_0000);
    always_comb
        is_inst[6] = |{BREAKi, SYSCALL, ERET, MFC0, MTC0};
    
    wire reg3_inst    = |{ADD, ADDU, SUB, SUBU, SLT, SLTU, ANDi, NORi, ORi, XORi, SLLV, SRAV, SRLV}; 
    wire imm16_inst   = |{ADDI, ADDIU, SLTI, SLTIU, ANDI, ORI, XORI};   
    wire hilo_inst    = |{DIV, DIVU, MULT, MULTU};
    wire sa_inst      = |{SLL, SRA, SRL};
    wire load_inst    = |{LB, LBU, LH, LHU, LW};
    wire store_inst   = |{SB, SH, SW};
    wire al_inst      = |{BGEZAL, BLTZAL, JAL, JALR};
    wire branch_type1 = BEQ | BNE;
    wire branch_type2 = |{BGEZ, BGTZ, BLEZ, BLTZ, BGEZAL, BLTZAL};
    wire jump_type1   = JR | JALR;
    wire jump_type2   = J | JAL;
    
    always_comb is_branch_inst = is_inst[3];
    
    wire [4 : 0] rs   = inst_i[25 : 21];
    wire [4 : 0] rt   = inst_i[20 : 16];
    wire [4 : 0] rd   = inst_i[15 : 11];
    wire [15 : 0] imm = inst_i[15 : 0];
    wire [4 : 0] sa   = inst_i[10 : 6];
    
    /* ************* Instr Excption ************* */
    always_comb Sys_exc = SYSCALL;
    always_comb Bp_exc = BREAKi;
    always_comb RI_exc = ~(|is_inst);
    always_comb ERET_exc = ERET;
    
    /* ********** registers read related ********** */
    always_comb         raddr1_o = rs;
    always_comb         raddr2_o = rt;
    
    reg [`REG_WIDTH - 1 : 0]   reg_rdata1;
    reg [`REG_WIDTH - 1 : 0]   reg_rdata2;
    always_comb reg_rdata1 = reg_rdata1_i;
    always_comb reg_rdata2 = reg_rdata2_i;
    
    /* ************* CP0 Register related *************** */
    always_comb         cp0_raddr_o = rd;
    always_comb         mtc_wen = MTC0;
    always_comb         mtc_waddr = rd;
    always_comb         mtc_wdata = reg_rdata2;
    
    /* ************* HILO registers related ****************** */
    always_comb whilodata_o = reg_rdata1;
    always_comb whien_o = MTHI;
    always_comb wloen_o = MTLO;
    
    reg [`REG_WIDTH - 1 : 0]    hidata;
    reg [`REG_WIDTH - 1 : 0]    lodata;
    always_comb                 hidata = rhidata_i;
    always_comb                 lodata = rlodata_i;
    
    /* ************** Determain opprands ************** */
    // opprand 1
    always_comb
        case ({|{reg3_inst, hilo_inst, imm16_inst, load_inst, store_inst}, 
                    sa_inst, LUI, al_inst, MFHI, MFLO, MFC0})
            7'b1000000:     opprand1_o = reg_rdata1;
            7'b0100000:     opprand1_o = {17'b0_0000_0000_0000_0000, sa};
            7'b0010000:     opprand1_o = {imm, 16'h0000};
            7'b0001000:     opprand1_o = pc_i + 8;
            7'b0000100:     opprand1_o = hidata;
            7'b0000010:     opprand1_o = lodata;
            7'b0000001:     opprand1_o = cp0_rdata_i;
            default:        opprand1_o = `ZERO_REG;
        endcase
        
    always_comb
        case ({|{reg3_inst, hilo_inst, sa_inst}, load_inst | store_inst, 
                    |{ADDI, ADDIU, SLTIU, SLTI}, |{ANDI, ORI, XORI}})
            4'b1000:        opprand2_o = reg_rdata2;
            4'b0100:        opprand2_o = {{16{imm[15]}}, imm};
            4'b0010:        opprand2_o = {{16{imm[15]}}, imm}; 
            4'b0001:        opprand2_o = {16'h0000, imm};  
            default:        opprand2_o = `ZERO_REG;
        endcase
        

    /* ************** Determain ALU opcode ************** */
    wire alu_add = |{ADD, ADDI, load_inst, store_inst};
    wire alu_addu = ADDU | ADDIU;
    wire alu_lt = SLT | SLTI;
    wire alu_ltu = SLTU | SLTIU;
    wire alu_and = ANDi | ANDI;
    wire alu_or = ORi | ORI;
    wire alu_xor = XORi | XORI;
    wire alu_sll = SLLV | SLL;
    wire alu_sra = SRAV | SRA;
    wire alu_srl = SRLV | SRL;
    wire alu_lui = |{LUI, al_inst, MFHI, MFLO, MFC0};
    reg [`ALUOP_WIDTH - 1 : 0]  alu_op1;
    reg [`ALUOP_WIDTH - 1 : 0]  alu_op2;
    reg [`ALUOP_WIDTH - 1 : 0]  alu_op3;
    always_comb
        case ({alu_add, alu_addu, alu_lt, alu_ltu, alu_and, alu_or, alu_xor, alu_sll, alu_sra, alu_srl, alu_lui})
            11'b1000_0000_000:      alu_op1 = `ADD_ALU;
            11'b0100_0000_000:      alu_op1 = `ADDU_ALU;
            11'b0010_0000_000:      alu_op1 = `LessThan_ALU;
            11'b0001_0000_000:      alu_op1 = `LessThanU_ALU;
            11'b0000_1000_000:      alu_op1 = `AND_ALU;
            11'b0000_0100_000:      alu_op1 = `OR_ALU;
            11'b0000_0010_000:      alu_op1 = `XOR_ALU;
            11'b0000_0001_000:      alu_op1 = `SLL_ALU;
            11'b0000_0000_100:      alu_op1 = `SRA_ALU;
            11'b0000_0000_010:      alu_op1 = `SRL_ALU;
            11'b0000_0000_001:      alu_op1 = `LUI_ALU;
            default:                alu_op1 = 5'b00000;
        endcase
    always_comb
        case ({SUB, SUBU, DIV, DIVU, MULT, MULTU, NORi})
            7'b1000_000:            alu_op2 = `SUB_ALU;
            7'b0100_000:            alu_op2 = `SUBU_ALU;
            7'b0010_000:            alu_op2 = `DIV_ALU;
            7'b0001_000:            alu_op2 = `DIVU_ALU;
            7'b0000_100:            alu_op2 = `MUL_ALU;
            7'b0000_010:            alu_op2 = `MULU_ALU;
            7'b0000_001:            alu_op2 = `NOR_ALU;
            default:                alu_op2 = 5'b00000;
        endcase
    always_comb aluop_o = alu_op1 | alu_op2;
    
    /* ************** Register write signal ************** */
    always_comb 
        wregenable_o = |{reg3_inst, imm16_inst, sa_inst, al_inst, LUI, MFHI, MFLO, load_inst, MFC0};
        
    wire wregrd = |{reg3_inst, MFHI, MFLO, sa_inst};
    wire wregrt = |{imm16_inst, LUI, load_inst, MFC0};
    always_comb
        case ({wregrd, wregrt, al_inst})
            3'b100:     wregaddr_o = rd;
            3'b010:     wregaddr_o = rt;
            3'b001:     wregaddr_o = 32'd31;
            default:    wregaddr_o = `ZERO_REG_ADDR;
        endcase

    /* ************* Branch related ****************** */
    wire [`PC_WIDTH - 1 : 0] branch_offset = {{14{inst_i[15]}}, inst_i[15 : 0], 2'b00};
    wire [27 : 0]            jump_offset   = {inst_i[25 : 0], 2'b00};
    wire [`PC_WIDTH - 1 : 0] delay_slot_pc = pc_i + 4;
    
    always_comb begin
        case ({branch_type1, branch_type2, jump_type1, jump_type2})
            4'b1000:    branch_addr_o = $unsigned(branch_offset) + $unsigned(delay_slot_pc);
            4'b0100:    branch_addr_o = $unsigned(branch_offset) + $unsigned(delay_slot_pc);
            4'b0010:    branch_addr_o = reg_rdata1;
            4'b0001:    branch_addr_o = {delay_slot_pc[31 : 28], jump_offset};
            default:    branch_addr_o = 32'h0000_0000;
        endcase
    end
    
    wire reg1_zero = ~(|reg_rdata1);
    wire reg1_pos  = (~reg_rdata1[31]) & (|reg_rdata1);
    wire reg1_neg  = reg_rdata1[31];
    
    wire beq_flag  = BEQ & (reg_rdata1 == reg_rdata2);
    wire bne_flag  = BNE & (reg_rdata1 != reg_rdata2);
    wire bgez_flag = (BGEZ | BGEZAL) & (reg1_pos | reg1_zero);
    wire bgtz_flag = BGTZ & reg1_pos;
    wire blez_flag = BLEZ & (reg1_zero | reg1_neg);
    wire bltz_flag = (BLTZ | BLTZAL) & reg1_neg;
    
    wire is_branch = |{beq_flag, bne_flag, bgez_flag, bgtz_flag, blez_flag, bltz_flag};
    
    always_comb branch_flag_o = |{is_branch, jump_type1, jump_type2};
    
    /* ************* Data read/write related ****************** */
    
    always_comb rdataen_o = (LB | LBU) ? 4'b0001 : (LH | LHU) ? 4'b0011 : LW ? 4'b1111 : 4'b0000;
    always_comb rdatasigned_o = (LB | LH | LW);
    always_comb wdataen_o = SB ? 4'b0001 : SH ? 4'b0011 : SW ? 4'b1111 : 4'b0000;
    always_comb wdata_o = (SB | SH | SW) ? reg_rdata2 : `ZERO_REG;
    
endmodule
